import java.util.*;
import java.io.*;

public class ASFParser
{
    private String[] fileLines = null; //Contains the inputted lines of the file
    private int currentLine = 0;
    private String curLine = "";
    public Bone root = null;
    public ArrayList<Bone> skeleton = null;

   public static void main(String[] args)
   {
       ASFParser program = new ASFParser();
       program.parseASF("07.asf");
   }

    public ArrayList<Bone> parseFile(String filename)
    {
        this.currentLine = 0;
        this.curLine = "";
        this.fileLines = getFile(filename);

        root = new Bone("root",true);
        parsePrelim();
        ArrayList<Bone> bones = parseBones(root);
        bones = parseHierarchy(bones);
        //printBones(bones);
        //return null;
        return bones;
    }

    public void parseASF(String filename)
    {
        this.skeleton = this.parseFile(filename);
    }

    private ArrayList<Bone> parseHierarchy(ArrayList<Bone> bs)
    {
        ParseRec p = null;
        getNextLine();
        p = parseLine("begin", 1); //ignore begin
        while (!p.ok)
        {
            getNextLine();
            p = parseLine("begin",1); //ignore begin
        }
        getNextLine();
        p = parseLine("end", 1);
        while (!p.ok)
        {
            Bone parentBone = bs.get(searchBone(p.words[0], bs));
            ArrayList<Bone> childBones = new ArrayList<Bone>();

            for (int i = 1; i < p.words.length; i++)
            {
                Bone b = bs.get(searchBone(p.words[i], bs));
                b.parent = parentBone;
                childBones.add(b);
            }

            parentBone.children = childBones;

            getNextLine();
            p = parseLine("end", 1);
        }
        return bs;
    }

    //Searches for the bone with name n
    public int searchBone(String n, ArrayList<Bone> bones)
    {
        for (int i = 0; i < bones.size(); i++)
        {
            if (bones.get(i).getName().toLowerCase().equals(n.toLowerCase()))
            {
                return i;
            }
        }
        return -1;
    }

    private void parsePrelim()
    {
        ParseRec p = null;

        while (notEOF())
        {

            p = parseLine(":version",1); //ignore version
            if (!p.ok)
            {
                p = parseLine(p,":name",1); //parse name
                if (p.ok)
                {
                    //root.setName(getString(p,2));
                    root.setName("root");
                    getNextLine(); continue;
                }

                p = parseLine(p,":units",1); //parse units
                if (p.ok)
                {
                    getNextLine();

                    while (notEndOfSection(p))
                    {

                        p = parseLine("mass",1); //parse units
                        if (p.ok)
                        {
                            root.setGlobalMass(getReal(p,2));
                            getNextLine(); continue;
                        }

                        p = parseLine(p,"length",1); //parse units
                        if (p.ok)
                        {
                            root.setGlobalLength(getReal(p,2));
                            getNextLine(); continue;
                        }

                        p = parseLine(p,"angle",1); //parse units
                        if (p.ok)
                        {
                            root.setGlobalAngle(getString(p,2));
                        }

                        getNextLine();
                    }

                    continue;
                }

                p = parseLine(p,":root",1); //parse root
                if (p.ok)
                {
                    getNextLine();

                    while (notEndOfSection(p))
                    {

                        p = parseLine("order",1); //order
                        if (p.ok)
                        {
                            root.order = this.curLine;
                            getNextLine(); continue;
                        }

                        p = parseLine(p,"axis",1); //axis
                        if (p.ok)
                        {
                            root.rootAxis = getString(p,2);
                            getNextLine(); continue;
                        }

                        p = parseLine(p,"position",1); //position
                        if (p.ok)
                        {
                            root.position[0] = getReal(p,2);
                            root.position[1] = getReal(p,3);
                            root.position[2] = getReal(p,4);
                            getNextLine(); continue;
                        }

                        p = parseLine(p,"orientation",1); //orientation
                        if (p.ok)
                        {
                            root.orientation[0] = getReal(p,2);
                            root.orientation[1] = getReal(p,3);
                            root.orientation[2] = getReal(p,4);
                        }

                        getNextLine();
                    }

                    continue;
                }

                p = parseLine(p,":bonedata",1); //parse bonedata
                if (p.ok)
                {
                    return;
                }

            }

            getNextLine();
        }
    }

    private ArrayList<Bone> parseBones(Bone root)
    {
        ArrayList<Bone> bones = new ArrayList<Bone>();
        bones.add(root);
        ParseRec p = null;
        p = parseLine(":bonedata",1); //parse bonedata
        if (p.ok)
        {
            println("Found bonedata, adding bones...");//PRINTING!!!
            getNextLine();
            p = parseLine("",1);

            while (true)
            {
                BoneRec aBone = parseBone(p);
                if (aBone != null)
                {
                    p = aBone.p;
                    bones.add(aBone.b);
                }
                p = parseLine(":hierarchy",1); //parse name
                if (p.ok)
                {
                    break;
                }

            }
            println("Bones added.");//PRINTING!!!
            println("");//PRINTING!!!
            return bones;
        }
        return null;
    }

    private BoneRec parseBone(ParseRec p)
    {
        Bone newBone = null;
        int boneID = 0;
        String boneName = "temp_name";
        double[] boneDirection = new double[3];
        double boneLength = 0.0;
        double[] boneAxis = new double[3];
        double[][] boneLimits = new double[3][2];
        String boneDof = "";

        p = parseLine("begin",1);

        if (p.ok)
        {
            getNextLine();
            p = parseLine("",1);

             while (notEndOfBone(p))
             {

                 p = parseLine("id",1); //parse units
                 if (p.ok)
                 {
                     boneID = getInt(p,2);
                     getNextLine(); continue;
                 }

                 p = parseLine(p,"name",1); //parse units
                 if (p.ok)
                 {
                     boneName = getString(p,2);
                     getNextLine(); continue;
                 }

                 p = parseLine(p,"direction",1); //parse units
                 if (p.ok)
                 {
                     boneDirection[0] = getReal(p,2);
                     boneDirection[1] = getReal(p,3);
                     boneDirection[2] = getReal(p,4);
                     getNextLine(); continue;
                 }

                 p = parseLine(p,"length",1); //parse units
                 if (p.ok)
                 {
                     boneLength = getReal(p,2);
                     getNextLine(); continue;
                 }

                 p = parseLine(p,"axis",1); //parse units
                 if (p.ok)
                 {
                     boneAxis[0] = getReal(p,2);
                     boneAxis[1] = getReal(p,3);
                     boneAxis[2] = getReal(p,4);
                     getNextLine(); continue;
                 }

                 p = parseLine(p,"dof",1); //parse units
                 if (p.ok)
                 {
                     boneDof = curLine;
                     getNextLine(); continue;
                 }

                 p = parseLine(p,"limits",1); //parse units
                 if (p.ok)
                 {
                     int ls = 0;

                     p = parseLine("",1); //parse units

                     p.words = removeHead(p.words);

                     while (isLimit(p))
                     {
                         p.words[0] = p.words[0].substring(1,p.words[0].length());
                         boneLimits[ls][0] = getReal(p,1);
                         p.words[1] = p.words[1].substring(0,p.words[1].length()-1);
                         boneLimits[ls][1] = getReal(p,2);

                         ls++;
                         getNextLine();
                         p = parseLine("",1); //parse units
                     }
                 }
                 
                 p = parseLine("end",1);
                 if (p.ok)
                 {
                     getNextLine();
                     break;
                 }

                getNextLine();
            }
            return new BoneRec(p,
            new Bone(boneID,boneName,boneDirection,boneLength,boneAxis,boneDof,
            boneLimits));
        }
        return null;
    }

    private void println(String s)
    {
        System.out.println(s);
    }

    private String[] getFile(String filename)
    {
       File file = new File(filename);
       String line;
       ArrayList<String> lineList;
       lineList = new ArrayList<String>();
       try
       {
          Scanner input = new Scanner(file);
          while (input.hasNextLine())
          {
             line = input.nextLine();
             lineList.add(line);
          }
          input.close();
       }
       catch (IOException err)
       {
          fail("Error whilst reading file.");
       }
       String[] lines = new String[lineList.size()];
       lineList.toArray(lines);
       return lines;
    }

    private void getNextLine()
    {
        try
        {
            String res = this.fileLines[currentLine];
            currentLine++;
            int p = res.indexOf('#');
            if (p != -1)
            {
                res = res.substring(0,p);
            }

            this.curLine = res.trim();
        }
        catch (Exception e)
        {
            this.curLine = null;
        }
    }

    //parses the given line into parts, returning a list of parts
    //and a record containing a boolean value indicating whether
    //a test word is in the given position - case sensitive
    private ParseRec parseLineCS(String testWord, int posOfWord)
    {
        String[] ws = this.curLine.split(" ");
        return new ParseRec(ws[posOfWord-1].equals(testWord), ws);
    }

    //parses the given line into parts, returning a list of parts
    //and a record containing a boolean value indicating whether
    //a test word is in the given position - case insensitive
    private ParseRec parseLine(String testWord, int posOfWord)
    {
        try
        {
            String[] ws = this.curLine.split(" ");
            return new ParseRec(
            ws[posOfWord-1].toLowerCase().equals(testWord.toLowerCase()), ws);
        }
        catch (Exception e)
        {
            String[] ws = this.curLine.split(" ");
            return new ParseRec(false,ws);
        }
    }

    //takes an existing parseRec and tests it
    private ParseRec parseLine(ParseRec p, String testWord, int posOfWord)
    {
        try
        {
            p.ok = p.words[posOfWord-1].toLowerCase().equals(testWord.toLowerCase());
            return p;
        }
        catch (Exception e)
        {
            p.ok = false;
            return p;
        }
    }

    private double getReal(ParseRec p, int index)
    {
        try
        {
            return Double.parseDouble(p.words[index-1]);
        }
        catch (Exception e)
        {
            fail("Invalid formatting. Real expected on line "
            + this.currentLine);
            return 0;
        }
    }

    private int getInt(ParseRec p, int index)
    {
        try
        {
            return Integer.parseInt(p.words[index-1]);
        }
        catch (Exception e)
        {
            fail("Invalid formatting. Integer expected on line "
            + this.currentLine);
            return 0;
        }
    }

    private String getString(ParseRec p, int index)
    {
        try
        {
            return p.words[index-1];
        }
        catch (Exception e)
        {
            fail("Invalid formatting. String expected on line "
            + this.currentLine);
            return "";
        }
    }
    
    private String[] removeHead(String[] input)
    {
        String[] res = new String[input.length-1];
        for (int i = 1; i < input.length; i++)
        {
            res[i-1] = input[i];
        }
        return res;
    }

    private boolean notEOF()
    {
        return (this.curLine != null);
    }

    private boolean notEndOfSection(ParseRec p)
    {
        return (p.words[0].charAt(0) != ':');
    }

    private boolean isLimit(ParseRec p)
    {
        return (p.words[0].charAt(0) == '(');
    }

    private boolean notEndOfBone(ParseRec p)
    {
        return (!p.words[0].equals("end"));
    }

    public void printBones(ArrayList<Bone> bs)
    {
        println("------------------------------------------------------");
        println("** LOADED BONES :                                   **");
        println("------------------------------------------------------");
        for (int i = 0; i < bs.size(); i++)
        {
            bs.get(i).print();
        }
        println("------------------------------------------------------");
    }

    private void fail(String msg)
    {
       System.err.println("Error: " + msg);
       System.exit(1);
    }
}
